﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Apache.Cassandra;
using Lucene.Net.Search;
using System.Threading;
using Lucene.Net.Documents;
using Lucene.Net.Analysis;
using System.IO;
using Lucene.Net.Analysis.Tokenattributes;
using System.Collections;
using Lucene.Net.Index;
using Lucandra.Convenience;
using System.Security.Cryptography;
using Lucandra.Net;
using Lucandra.Net.Connection.Pooling;
using Lucandra.Configuration;
using System.Diagnostics;

namespace Lucandra.Net
{
    public class LucandraWriter
    {
        #region Fields

        private readonly string indexName;
        private readonly string keyspace;

        private bool autoCommit;
        private Similarity similarity;
        private LucandraHelper lucandraHelper;

        private static RNGCryptoServiceProvider rng;

        #endregion

        #region Type Initializer

        static LucandraWriter()
        {
            rng = new RNGCryptoServiceProvider();
        }

        #endregion

        #region Constructors

        /// <summary>
        /// Creates a new LucandraWriter using the default index name and the specified auto-commit behavior.
        /// </summary>
        /// <param name="autoCommit">True to auto-persist changes to Cassandra, false to call Commit() manually.</param>
        public LucandraWriter(bool autoCommit)
            : this(LucandraConfigurationSection.GetConfig().DefaultIndexName, autoCommit)
        {
        }

        /// <summary>
        /// Creates a new LucandraWriter using the default index name and the default auto-commit behavior.
        /// </summary>
        public LucandraWriter()
            : this(LucandraConfigurationSection.GetConfig().DefaultIndexName, LucandraConfigurationSection.GetConfig().DefaultAutoCommit)
        {
        }

        /// <summary>
        /// Creates a new LucandraWriter using the specified index name and the default auto-commit behavior.
        /// </summary>
        /// <param name="indexName">The name of the index that this LucandraWriter should write to.</param>
        public LucandraWriter(string indexName)
            : this(indexName, LucandraConfigurationSection.GetConfig().DefaultAutoCommit)
        {
        }

        /// <summary>
        /// Creates a new LucandraWriter using the specified index name and the specified auto-commit behavior.
        /// </summary>
        /// <param name="indexName">The name of the index that this LucandraWriter should write to.</param>
        /// <param name="autoCommit">True to auto-persist changes to Cassandra, false to call Commit() manually.</param>
        public LucandraWriter(string indexName, bool autoCommit)
        {
            // Determine the keyspace to write to.
            this.keyspace = KeyspaceManager.GetKeyspaceFromIndexName(indexName);
            KeyspaceManager.AssertKeyspaceExists(indexName);

            this.indexName = indexName;
            this.autoCommit = autoCommit;
            this.similarity = Similarity.GetDefault();
            this.lucandraHelper = new LucandraHelper(this.keyspace, this.indexName);
        }

        #endregion

        #region Methods [public]

        public void AddDocument(Document doc, Analyzer analyzer)
        {
            byte[] randomIntBytes = new byte[4];
            rng.GetBytes(randomIntBytes);
            int random = BitConverter.ToInt32(randomIntBytes, 0);

            string docID = ((long)(DateTime.UtcNow.ToFileTime() + (random * DateTime.UtcNow.ToFileTime()))).ToString("X16");

            lucandraHelper.AddDocument(docID, doc, analyzer, this.similarity);
            if (this.autoCommit)
                lucandraHelper.BatchMutate(ConsistencyLevel.ONE);
        }

        public void DeleteDocuments(Query query)
        {
            var reader = new LucandraReader(this.indexName);
            var searcher = new IndexSearcher(reader);
            
            var docs = searcher.Search(query, 1000000);

            for (int i = 0; i < docs.totalHits; i++)
            {
                var doc = reader.Document(docs.scoreDocs[i].doc);
                var lucandraDocument = reader.GetLucandraDocument(docs.scoreDocs[i].doc);
                lucandraHelper.DeleteLucandraDocument(lucandraDocument);
            }

            if (autoCommit)
                lucandraHelper.BatchMutate(ConsistencyLevel.ONE);
        }

        public void DeleteDocuments(Term term)
        {
            this.DeleteDocuments(new TermQuery(term));
        }

        public void UpdateDocument(Term updateTerm, Document doc, Analyzer analyzer) 
        {
            this.DeleteDocuments(updateTerm);
            this.AddDocument(doc, analyzer);
        }

        public int DocCount() 
        {
            throw new NotImplementedException("There is currently not an efficient way to determine the number of documents in the index.");
        }

        public bool IsAutoCommit() 
        {
            return autoCommit;
        }

        public void SetAutoCommit(bool autoCommit) 
        {
            this.autoCommit = autoCommit;
        }

        public void Commit()
        {
            if (!autoCommit)
                lucandraHelper.BatchMutate(ConsistencyLevel.ONE);
        }

        #endregion
    }
}
